#include "logger.h"


using namespace std;

#define MSGPERM 0600    // msg queue permission
Logger *Logger::inst;

Log::Log(int mq, int mode, const char* name, int pid) {
    m_msgqid = mq;
    m_mode = mode;
    strcpy(m_name, name);
    m_pid = pid;

}

int Log::Debug(string s) {

    time_t rawtime;
    time(&rawtime);

    msg_buf msg;
    msg.mtype = DEBUG_TYPE; // set the type of message
    struct tm * timeinfo;
    timeinfo = localtime(&rawtime);
    size_t time_size = strftime(msg.data.mtext, MSGTXTLEN, "%c : ", timeinfo);
    size_t length = s.copy(msg.data.mtext + time_size, MSGTXTLEN - strlen(msg.data.mtext) - 1, 0);
    msg.data.mtext[length + time_size] = '\0';
    msg.data.pid = m_pid;
    int rc = msgsnd(m_msgqid, &msg, sizeof (msg.data), 0); // the last param can be: 0, IPC_NOWAIT, MSG_NOERROR, or IPC_NOWAIT|MSG_NOERROR.
    if (rc < 0) {
        return -1;
    }
    return length;

}

int Log::Warning(string s) {

    time_t rawtime;
    time(&rawtime);
    msg_buf msg;
    msg.mtype = WARNING_TYPE; // set the type of message
    struct tm * timeinfo;
    timeinfo = localtime(&rawtime);
    size_t time_size = strftime(msg.data.mtext, MSGTXTLEN, "%c : ", timeinfo);
    size_t length = s.copy(msg.data.mtext + time_size, MSGTXTLEN - strlen(msg.data.mtext) - 1, 0);
    msg.data.mtext[length + time_size] = '\0';
    msg.data.pid = m_pid;

    int rc = msgsnd(m_msgqid, &msg, sizeof (msg.data), 0); // the last param can be: 0, IPC_NOWAIT, MSG_NOERROR, or IPC_NOWAIT|MSG_NOERROR.
    if (rc < 0) {
        return -1;
    }
    return length;

}

int Log::Error(string s) {
    time_t rawtime;
    time(&rawtime);
    msg_buf msg;
    msg.mtype = ERROR_TYPE; // set the type of message
    struct tm * timeinfo;
    timeinfo = localtime(&rawtime);
    size_t time_size = strftime(msg.data.mtext, MSGTXTLEN, "%c : ", timeinfo);
    size_t length = s.copy(msg.data.mtext + time_size, MSGTXTLEN - strlen(msg.data.mtext) - 1, 0);
    msg.data.mtext[length + time_size] = '\0';
    msg.data.pid = m_pid;

    int rc = msgsnd(m_msgqid, &msg, sizeof (msg.data), 0); // the last param can be: 0, IPC_NOWAIT, MSG_NOERROR, or IPC_NOWAIT|MSG_NOERROR.
    if (rc < 0) {
        return -1;
    }
    return length;

}

int Logger::init(size_t buffsize, const char* file) {

    if (!inst) {
        Logger::inst = new Logger();
        init_buf msg;
        msg.mtype = INIT_TYPE; // set the type of message
        msg.data.buffsize = buffsize;
        strcpy(msg.data.file_name, file);
        int rc = msgsnd(inst->m_msgqid, &msg, sizeof (msg.data), 0); // the last param can be: 0, IPC_NOWAIT, MSG_NOERROR, or IPC_NOWAIT|MSG_NOERROR.
        if (rc < 0) {
            return -1;
        }
    }

    return 0;

}

int Log::logout() {
    if (getpid() != m_pid) {
        return 1;
    }
    msg_buf msg;
    msg.mtype = LOGOUT_TYPE; // set the type of message
    msg.data.pid = m_pid;


    int rc = msgsnd(m_msgqid, &msg, sizeof (msg.data), 0); // the last param can be: 0, IPC_NOWAIT, MSG_NOERROR, or IPC_NOWAIT|MSG_NOERROR.
    if (rc < 0) {
        return -1;
    }
    return 0;
}

Log Logger::subs(int mode, const char * name) {
    subs_buf msg;
    msg.mtype = SUBS_TYPE; // set the type of message
    msg.data.mode = mode;
    msg.data.pid = getpid();
    strncpy(msg.data.name, name, NAME_LEN - 1);
    msg.data.name[NAME_LEN - 1] = '\0';

    int rc = msgsnd(inst->m_msgqid, &msg, sizeof (msg.data), 0); // the last param can be: 0, IPC_NOWAIT, MSG_NOERROR, or IPC_NOWAIT|MSG_NOERROR.
    if (rc < 0) {
        exit(1);
    }
    return Log(inst->m_msgqid, mode, name, msg.data.pid);
}

Logger::Logger() {
    m_msgqid = msgget(IPC_PRIVATE, MSGPERM | IPC_CREAT | IPC_EXCL);
    if (m_msgqid < 0) {
        exit(1);
    }
    pid_t pid = fork();
    if (pid == 0) {
        buf_start = buf_ptr = 0;
        m_sleep_time = 1;
        msg_buf msg;
        msqid_ds msq_data;
        init_buf init_msg;
        map<int, Record> processes;

        //waiting for init
        while (processes.size() || getppid() > 1) {

            msgctl(m_msgqid, MSG_STAT, &msq_data);
            if (msq_data.msg_qnum) {
                int rc = msgrcv(m_msgqid, &init_msg, sizeof (init_msg.data), 0, 0);
                if (rc < 0) {
                    exit(1);
                }
                if (init_msg.mtype == INIT_TYPE) {
                    buffer = new char[init_msg.data.buffsize]();
                    out = new ofstream;
                    buf_size = init_msg.data.buffsize;
                    out->open(init_msg.data.file_name);
                    break;
                } else {
                    cout << "First need to init logger" << endl;
                    exit(1);
                }

            } else {
                usleep(m_sleep_time);
            }
        }


        //main cycle
        while (processes.size() || getppid() > 1) {

            msgctl(m_msgqid, MSG_STAT, &msq_data);
            if (msq_data.msg_qnum) {
                int rc = msgrcv(m_msgqid, &msg, sizeof (msg.data), 0, 0);
                if (rc < 0) {
                    exit(1);
                }
                int log_id = msg.data.pid;
                switch (msg.mtype) {
                    case INIT_TYPE:
                        //TODO
                        
                        break;
                    case LOGOUT_TYPE:
                        processes.erase(log_id);
                        break;
                    case DEBUG_TYPE:
                        if (processes.count(msg.data.pid)) {
                            if (processes[msg.data.pid].mode == DEBUG_MODE) {
                                this->buf_write(processes[msg.data.pid].name, msg.data.mtext, MSGTXTLEN);
                            }
                        }
                        break;
                    case WARNING_TYPE:
                    case ERROR_TYPE:
                        if (processes.count(msg.data.pid)) {
                            if (processes[msg.data.pid].mode == RELEASE_MODE || processes[msg.data.pid].mode == DEBUG_MODE) {
                                this->buf_write(processes[msg.data.pid].name, msg.data.mtext, MSGTXTLEN);
                            }
                        }
                        break;
                    case SUBS_TYPE:
                        processes[log_id] = Record(((subs_buf*) & msg)->data.name, log_id, ((subs_buf*) & msg)->data.mode);
                        break;
                }

            } else {
                usleep(m_sleep_time);
            }
        }


        // remove the queue
        int rc = msgctl(m_msgqid, IPC_RMID, NULL);
        if (rc < 0) {
            exit(1);
        }
        this->buf_dump();
        out->flush();
        out->close();
        exit(0);

    } else if (pid == -1) {
        exit(1);
    }
}

int Logger::buf_write(char *name, char * str, size_t len) {
    //can't fit in
    string s = string(name) + " : " + string(str) + "\n";
    if (s.length() > (buf_size + buf_start - buf_ptr - 1) % buf_size) {
        this->buf_dump();
        buf_start = buf_ptr;
        *out << s;
    } else {
        int i;
        for (i = 0; i < s.length(); i++) {
            buffer[(buf_ptr + i) % buf_size] = s[i];
        }
        buf_ptr = (buf_ptr + i) % buf_size;
    }
    return 0;
}

int Logger::buf_dump() {
    if (buf_start <= buf_ptr) {
        out->write(buffer + buf_start, buf_ptr - buf_start);
    } else {
        out->write(buffer + buf_start, buf_size - buf_start);
        out->write(buffer, buf_ptr);
    }
    return 0;
}